package com.zhaosc.mybatis.shared.table;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

@Intercepts({
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
				RowBounds.class, ResultHandler.class }),
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
				RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }),
		@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class TableSharedInterceptor implements Interceptor {
	


	@Override
	public Object intercept(Invocation invocation) throws Throwable {

		Object[] args = invocation.getArgs();

		MappedStatement mappedStatement = (MappedStatement) args[0];
		Object object = args[1];

		Configuration configuration = mappedStatement.getConfiguration();

		String mapperId = mappedStatement.getId();

		String mapperClass = mapperId.substring(0, mapperId.lastIndexOf("."));

		TableShared tableShared = Class.forName(mapperClass).getAnnotation(TableShared.class);
		if (tableShared == null) {
			return invocation.proceed();
		}
		String feildName = tableShared.feild();
		int tableNum = tableShared.tableNum();

		BoundSql boundSql = mappedStatement.getBoundSql(object);

		boolean isSelect = mappedStatement.getSqlCommandType() == SqlCommandType.SELECT;

		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

		Set<String> map = parameterMappings.stream().filter((t) -> {
			return ParameterMode.IN.equals(t.getMode());
		}).map((t) -> {
			String[] propertys = t.getProperty().split("\\.");
			if (propertys.length == 2) {
				return propertys[1];
			}
			return propertys[0];
		}).distinct().collect(Collectors.toSet());

		if (map.contains(feildName)) {
			if (object instanceof HashMap) {
				HashMap paraMap = (HashMap) object;
				if (paraMap.containsKey("collection")) {
					Collection<?> collection = (Collection<?>) paraMap.get("collection");
					Iterator<?> iterator = collection.iterator();
					Field field = null;
					Map<String, ParamMap> mapcollect = Maps.newHashMap();
					while (iterator.hasNext()) {
						Object obj = iterator.next();
						if (field == null) {
							field = obj.getClass().getDeclaredField(feildName);
							field.setAccessible(true);
						}
						int tableNo = Math.abs(field.get(obj).hashCode() % tableNum);
						String key = mapperId + "-" + tableNo;
						if (!mapcollect.containsKey(key)) {
							ParamMap<Object> paramMap = new ParamMap<>();
							List list = Lists.newArrayList();
							paramMap.put("collection", list);
							paramMap.put("arg0", list);
							paramMap.put("list", list);
							mapcollect.put(key, paramMap);
						}
						((Collection) mapcollect.get(key).get("collection")).add(obj);
					}

					Iterator<String> iteratorKey = mapcollect.keySet().iterator();
					List<Object> result = Lists.newArrayList();
					int total = 0;
					while (iteratorKey.hasNext()) {
						String mapperIdNew = iteratorKey.next();
						args[0] = configuration.getMappedStatement(mapperIdNew);
						args[1] = mapcollect.get(mapperIdNew);
						Object obj = invocation.proceed();
						if (obj != null) {
							if (obj instanceof List) {
								List objlist = ((List) obj);
								if (CollectionUtils.isNotEmpty(objlist)) {
									result.addAll(objlist);
								}

							}
							if (!isSelect) {
								total = total + (Integer) obj;
							}
							result.add(obj);
						}
					}

					if (!isSelect) {
						return total;
					}
					return result;

				} else if (paraMap.containsKey("array")) {

				}
				Object value = paraMap.get(feildName);
				int tableNo = Math.abs(value.hashCode() % tableNum);
				args[0] = configuration.getMappedStatement(mapperId + "-" + tableNo);
				return invocation.proceed();
			} else {
				Field field = object.getClass().getDeclaredField(feildName);
				field.setAccessible(true);
				int tableNo = Math.abs(field.get(object).hashCode() % tableNum);
				args[0] = configuration.getMappedStatement(mapperId + "-" + tableNo);
				return invocation.proceed();
			}
		}

		List<Object> result = Lists.newArrayList();
		int total = 0;
		for (int i = 0; i < tableNum; i++) {
			args[0] = configuration.getMappedStatement(mapperId + "-" + i);
			Object obj = invocation.proceed();
			if (obj != null) {
				if (isSelect) {
					List objlist = ((List) obj);
					if (CollectionUtils.isNotEmpty(objlist)) {
						result.addAll(objlist);
					}
				} else {
					total = total + (int) obj;
				}

			}
		}

		if (isSelect) {
			return result;
		} else {
			return total;
		}

	}

	@Override
	public Object plugin(Object target) {

		if (target instanceof Executor) {
			return Plugin.wrap(target, this);
		}
		return target;
	}

	@Override
	public void setProperties(Properties properties) {

	}

}
